3.06. MongoDB
Разработчику
Аналитику
Тестировщику
Архитектору
Инженеру
MongoDB
MongoDB — это распределённая, документо-ориентированная система управления базами данных с открытым исходным кодом, изначально разработанная для поддержки высоконагруженных, динамически изменяющихся и масштабируемых приложений. В отличие от реляционных СУБД, где данные организуются в строгие табличные структуры с фиксированными схемами, MongoDB основана на концепции документа — автономной, самодостаточной единицы данных, хранящейся в бинарном представлении JSON (BSON). Такой подход позволяет гибко работать со сложными, вложенными и неоднородными структурами без необходимости предварительного проектирования нормализованной схемы.
Концептуальные основы
В реляционных СУБД ключевой строительный блок — строка таблицы: запись, состоящая из фиксированного набора полей, определённых заранее в схеме таблицы. Все строки одной таблицы имеют одинаковую структуру. В MongoDB аналогом строки является документ, а таблицы — коллекция. Однако принципиальное отличие в том, что коллекция не накладывает ограничений на структуру хранящихся в ней документов. Один документ может содержать поля name, age, hobbies; другой — лишь title и timestamp; третий — вложенный объект с произвольной глубиной вложенности. Такая гибкость называется схемой по записи (schema-on-write в реляционных системах против schema-on-read в документных), и она особенно ценна в условиях быстро меняющихся требований к данным, характерных для современных приложений.
Каждый документ в MongoDB обязан содержать поле _id, играющее роль первичного ключа. Значением _id может быть любое допустимое BSON-значение, однако по умолчанию, если при вставке документа поле _id не указано, драйвер MongoDB генерирует его как ObjectId — 12-байтовое значение, состоящее из временной метки создания документа, идентификатора машины, идентификатора процесса и счётчика. Такая структура позволяет однозначно идентифицировать документы в распределённой среде без централизованного координатора и обеспечивает естественную сортировку по времени создания.
База данных в MongoDB — это логический контейнер, объединяющий одну или несколько коллекций, а также другие объекты: индексы, представления, пользовательские функции. База данных является основной единицей административного управления: на неё накладываются права доступа, политики резервного копирования, определяются параметры репликации. Одна инсталляция MongoDB (точнее, один сервер mongod или кластер mongos) может содержать множество баз данных, каждая со своей собственной логикой и набором коллекций.
Таким образом, иерархия организации данных в MongoDB выглядит следующим образом:
- Сервер/Кластер MongoDB →
- База данных (Database) →
- Коллекция (Collection) →
- Документ (Document) →
- Поля (Fields) с BSON-значениями.
- Документ (Document) →
- Коллекция (Collection) →
- База данных (Database) →
Эта иерархия напрямую отражается в синтаксисе оболочки MongoDB: db.collectionName.find(...) — где db — текущая база данных, а collectionName — её коллекция.
BSON
MongoDB использует BSON (Binary JSON) — расширение JSON, оптимизированное для эффективного хранения и быстрого обхода. Хотя BSON часто называют «бинарным JSON», это самостоятельный двоичный формат с собственными типами данных и правилами сериализации.
Основные отличия BSON от обычного JSON:
-
Типизация. В JSON существует ограниченный набор типов: строки, числа (не делятся на int/float), булевы значения,
null, объекты и массивы. BSON, напротив, поддерживает более двадцати явных типов, включая:- 32-битные и 64-битные целые (
int32,int64); - 32-битные и 64-битные числа с плавающей точкой (
double,decimal128); - логические значения;
null;- строки;
- двоичные данные (
BinData); - объекты (
Object); - массивы;
- временные метки (
Date); - регулярные выражения;
ObjectId;- минимум и максимум (
MinKey,MaxKey); - JavaScript-код (
Code) — устаревший, но поддерживаемый тип.
Явная типизация позволяет избежать неоднозначностей при десериализации и обеспечивает корректную сортировку и сравнение (например, целое число 100 будет корректно сравниваться как число, а не как строка
"100"). - 32-битные и 64-битные целые (
-
Эффективность. BSON хранится в бинарной форме, что позволяет избежать накладных расходов на парсинг текстового JSON при каждом чтении или записи. Данные могут быть частично десериализованы без полной загрузки документа в память — например, драйвер может быстро извлечь значение
_id, не обрабатывая остальное содержимое документа. Это критически важно для производительности в высоконагруженных системах. -
Поддержка двоичных данных. BSON включает специальный тип
BinData, что делает возможным хранение изображений, аудио, видео или сериализованных объектов непосредственно в документах (хотя для очень больших бинарных объектов рекомендуется использовать GridFS — расширение MongoDB для хранения файлов). -
Самоописываемость. Каждое поле в BSON-документе предваряется своим типом и длиной, а весь документ начинается с 4-байтового заголовка, содержащего общую длину документа. Это позволяет быстро пропускать документы при сканировании и эффективно читать их в произвольном порядке.
Поскольку BSON является строгим надмножеством JSON, любой корректный JSON-объект может быть преобразован в BSON и обратно без потерь информации (при условии, что числа не теряют точности — например, целые вне диапазона int64 могут быть преобразованы в double). Обратное неверно: не каждый BSON-документ может быть корректно представлен в виде JSON без дополнительных аннотаций (например, Date или ObjectId в JSON требуют специального оборачивания в объект { "$date": "..." }).
Экосистема MongoDB
MongoDB — это не только ядро СУБД (mongod). Это развитая экосистема, включающая инструменты для разработки, администрирования, мониторинга и развёртывания.
Ядро и сервисы
mongod— основной процесс сервера базы данных. Он отвечает за хранение данных, обработку запросов, управление индексами, транзакциями, репликацией и шардированием. Может работать как в standalone-режиме, так и в составе реплика-сета или шардированного кластера.mongos— маршрутизатор запросов для шардированных кластеров. Он принимает запросы от клиентов, определяет, к каким шардам они относятся, распределяет их и агрегирует результаты.mongosh— современная интерактивная оболочка MongoDB, заменяющая устаревшийmongo. Поддерживает синтаксис JavaScript, автодополнение, подсветку синтаксиса, историю команд и расширения. Является основным инструментом для интерактивной диагностики и администрирования.- MongoDB Atlas — облачная fully-managed платформа MongoDB, предоставляемая компанией MongoDB Inc. Управляет инфраструктурой, репликацией, шардированием, резервным копированием, обновлениями, мониторингом и безопасностью. Поддерживает развёртывание в AWS, Azure, Google Cloud.
Административные и разработческие инструменты
- MongoDB Compass — официальный графический интерфейс для MongoDB, позволяющий визуально исследовать данные, строить запросы без знания синтаксиса, анализировать производительность запросов, создавать и управлять индексами, настраивать схемы и валидаторы. Особенно полезен для анализа распределения значений полей и выявления «холодных» или «горячих» участков данных.
- MongoDB Shell Extensions — плагины для
mongosh, расширяющие его функциональность: поддержка TypeScript, улучшенное форматирование, интеграция с Atlas и т.д. - MongoDB Database Tools — набор утилит командной строки:
mongodump/mongorestore(резервное копирование и восстановление),mongoexport/mongoimport(экспорт/импорт в JSON или CSV),bsondump(просмотр BSON в человекочитаемом виде),mongofiles(управление GridFS).
Драйверы и ODM/ORM
MongoDB предоставляет официальные драйверы для всех основных языков: C, C++, C#, Go, Java, Node.js, Perl, PHP, Python, Ruby, Rust, Scala. Драйверы реализуют протокол обмена с сервером, обработку соединений, сериализацию BSON и базовую логику управления транзакциями.
Помимо драйверов, существуют высокоуровневые абстракции:
- Mongoose (Node.js) — popular ODM (Object Data Mapper), добавляющий строгую схему, валидацию, middleware и методы-хелперы.
- PyMongo + MongoEngine (Python) — PyMongo — низкоуровневый драйвер, MongoEngine — ODM поверх него.
- Spring Data MongoDB (Java) — часть экосистемы Spring, предоставляет шаблоны репозиториев, аннотации для сопоставления объектов и интеграцию с транзакционным менеджером Spring.
Дополнительные компоненты
- MongoDB Realm — платформа для создания full-stack приложений с синхронизацией данных между клиентом и сервером в реальном времени (ранее — Stitch).
- MongoDB Charts — инструмент визуализации данных, позволяющий строить графики и дашборды по данным MongoDB без написания кода.
- MongoDB Kafka Connector — интеграция с Apache Kafka для потоковой передачи изменений в MongoDB (Change Streams → Kafka).
Эта экосистема позволяет охватить весь жизненный цикл данных: от проектирования и разработки приложения до развёртывания, мониторинга и аналитики.
CRUD-операции
Операции создания, чтения, обновления и удаления в MongoDB реализованы через методы, работающие на уровне коллекций. Все они принимают BSON-объекты в качестве параметров и возвращают структурированные результаты (включая метаданные: количество затронутых документов, статус записи, время выполнения при профилировании и т.п.).
1. Вставка: insertOne и insertMany
Метод insertOne(document, options?) вставляет один документ в коллекцию. Если в документе отсутствует поле _id, драйвер автоматически генерирует для него ObjectId. Если _id указан явно и уже существует в коллекции, операция завершается ошибкой E11000 duplicate key error, поскольку _id всегда имеет уникальный индекс.
Метод insertMany(documentsArray, options?) позволяет вставить массив документов за одно обращение к серверу. Это существенно эффективнее, чем N вызовов insertOne, особенно при работе по сети. По умолчанию вставка выполняется последовательно и атомарно для каждого документа, но не для всего массива в целом: если при вставке k-го документа возникнет ошибка (например, дубликат _id), операция прекращается, и последующие документы не вставляются. Это поведение контролируется флагом ordered:
ordered: true(по умолчанию) — остановка при первой ошибке;ordered: false— сервер продолжает обработку оставшихся документов, несмотря на ошибки, и возвращает агрегированную информацию обо всех сбоях.
Оба метода принимают опцию writeConcern, определяющую уровень подтверждения записи. Например, { w: "majority", wtimeout: 5000 } означает: дождаться подтверждения от большинства узлов реплика-сета, но не более 5 секунд; при таймауте операция завершится с ошибкой, хотя запись могла быть выполнена частично.
2. Чтение: find и findOne
Метод findOne(filter?, projection?, options?) возвращает один документ, соответствующий фильтру (первый в порядке сканирования), или null, если совпадений нет. Он не возвращает курсор — результат сразу материализуется. Этот метод удобен для поиска по уникальному полю (включая _id).
Метод find(filter?, projection?, options?) возвращает курсор — объект, позволяющий лениво итерироваться по результатам запроса. Курсор извлекает данные пачками (batch size регулируется на стороне сервера и клиента). Это критически важно при работе с большими выборками: приложение получает данные по мере необходимости, не расходуя избыточную память.
Важно понимать, что find() без фильтра возвращает все документы коллекции. Это может привести к значительной нагрузке на сеть и клиент, если коллекция содержит миллионы записей. Всегда применяйте ограничения (limit, skip, sort) или фильтры в production-сценариях.
3. Обновление: updateOne, updateMany, replaceOne
MongoDB различает модификационные и заменяющие операции.
-
updateOne(filter, update, options?)иupdateMany(filter, update, options?)применяют операторы обновления ($set,$inc,$pushи др.) к существующим документам. Обновляется только указанные поля; остальная структура документа сохраняется. Поведение по умолчанию: если фильтр не находит ни одного документа — операция «тихо» завершается без изменений. -
replaceOne(filter, replacement, options?)полностью заменяет найденный документ новым объектом (кроме_id, который сохраняется, если не указан иначе). Все поля оригинала, отсутствующие вreplacement, теряются.
Ключевая опция — upsert: true. При её включении, если фильтр не находит совпадений, MongoDB вставляет новый документ. При этом:
- Если в
updateиспользуется$setOnInsert, его поля применяются только при вставке; - Поле
_idпри вставке генерируется автоматически, если не указано явно в фильтре или вreplacement/update.
Другие важные опции:
arrayFilters— позволяет адресовать конкретные элементы массива по условию, например:
updateMany({}, { $set: { "grades.$[g]": 100 } }, { arrayFilters: [ { g: { $gte: 90 } } ] }).hint— явное указание индекса для выполнения обновления.
4. Удаление: deleteOne, deleteMany
Методы deleteOne(filter, options?) и deleteMany(filter, options?) удаляют документы, соответствующие фильтру. Удаление не возвращает сами документы — только счётчик (deletedCount). Для получения удалённого документа следует использовать findOneAndDelete(filter, options?), который атомарно находит, возвращает и удаляет один документ.
Удаление всей коллекции через deleteMany({}) не удаляет метаданные коллекции: индексы, валидаторы, параметры TTL — сохраняются. Физическое освобождение дискового пространства происходит асинхронно (в зависимости от storage engine: WiredTiger выполняет фоновую компактизацию). Чтобы полностью уничтожить коллекцию со всей структурой — используется db.collection.drop().
Построение запросов
Запросы в MongoDB строятся с помощью BSON-объектов, содержащих операторы запроса — специальные ключи, начинающиеся с символа $.
1. Операторы сравнения
$eq,$ne— равно / не равно (явно указываются, если требуется семантика «точного совпадения», включая тип);$gt,$gte,$lt,$lte— строгое и нестрогое сравнение;$in,$nin— проверка вхождения значения в массив (или отсутствия в нём).
Пример:{ status: { $in: ["active", "pending"] } }.
Важно: сравнение значений разных типов подчиняется правилам BSON-сортировки: null < числа < строки < объекты < массивы < бинарные данные < ObjectId < булевы < даты < регулярные выражения < MinKey < MaxKey. Это влияет как на сортировку, так и на результаты $gt/$lt.
2. Логические операторы
$and— конъюнкция (по умолчанию используется при перечислении полей:{a: 1, b: 2}эквивалентно{ $and: [{a: 1}, {b: 2}] });$or— дизъюнкция;$not— логическое отрицание (применяется к одному условию:{ name: { $not: { $eq: "admin" } } });$nor— отрицание дизъюнкции ({ $nor: [{a: 1}, {b: 1}] }⇔ ниa=1, ниb=1).
Сложные запросы строятся рекурсивно. Например, выборка документов, где (age < 18 или age > 65) и status ≠ "blocked":
{
$and: [
{ $or: [ { age: { $lt: 18 } }, { age: { $gt: 65 } } ] },
{ status: { $ne: "blocked" } }
]
}
3. Операторы элементов и массивов
$exists: true|false— проверка наличия поля;$type: "<typeName>" | <typeNumber>— проверка типа значения (например,"string"или2);$all— все указанные элементы должны присутствовать в массиве (независимо от порядка);$size— точное совпадение размера массива (не поддерживает индексацию — требует сканирования);$elemMatch— условие применяется к одному и тому же элементу вложенного массива объектов.
Пример: найти студентов, у которых есть оценка ≥85 по предмету "math":
{ grades: { $elemMatch: { subject: "math", score: { $gte: 85 } } } }.
Курсоры
Как уже отмечалось, find() возвращает курсор — объект, поддерживающий цепочку методов для последовательной обработки результата.
-
.sort(sortSpec)— задаёт порядок сортировки:{ field: 1 }— по возрастанию,{ field: -1 }— по убыванию. Можно сортировать по нескольким полям. Важно: сортировка без подходящего индекса может потребовать сортировки в памяти (ограничение по умолчанию — 32 МБ; при превышении — ошибка, если не указанallowDiskUse: true). -
.limit(n)— ограничивает количество возвращаемых документов. Эффективно используется вместе сsort, чтобы получить топ-N результатов. -
.skip(n)— пропускает первыеnдокументов. Часто применяется для пагинации, но неэффективно при больших смещениях (skip(10000).limit(10)требует сканирования 10 010 документов). Для глубокой пагинации рекомендуется курсорная пагинация на основе последнего значения сортируемого поля. -
.projection({ field: 1 | 0 })— управляет возвращаемыми полями:1— включить,0— исключить (нельзя смешивать в одном запросе, кроме как для_id). Исключение тяжёлых полей (например,payload,logs) значительно снижает объём передаваемых данных. -
.hint(indexName)— явное указание индекса, что полезно при отладке или неоднозначных планах. -
.explain("executionStats")— возвращает детали выполнения запроса: использованный индекс, число просканированных и возвращённых документов (totalDocsExaminedvsnReturned), время выполнения. Ключевой инструмент для оптимизации.
Курсоры имеют ограниченное время жизни на сервере (обычно 10 минут неактивности). При превышении — ошибка cursor not found. В прикладном коде следует избегать длительных пауз между hasNext() и next().
Индексы
Индекс в MongoDB — это отдельная структура данных (B-дерево или его вариации), которая хранит отсортированные значения одного или нескольких полей и ссылки на соответствующие документы. Индексы позволяют избежать полного сканирования коллекции (COLLSCAN) и переходить к нужным документам напрямую (IXSCAN).
Основные типы индексов
- Однопольный индекс (
{ field: 1 }) — самый простой и часто используемый. - Составной индекс (
{ a: 1, b: -1 }) — эффективен для запросов, фильтрующих поa, или поaиb, или сортирующих по(a, b). Порядок полей критичен: префикс индекса должен совпадать с фильтром. - Уникальный индекс (
{ email: 1 }, { unique: true }) — гарантирует отсутствие дубликатов по указанному полю(ям). Ошибка при вставке/обновлении вызывает исключениеE11000. - Частичный индекс (
{ status: "active" }, { partialFilterExpression: { status: "active" } }) — индексирует только документы, удовлетворяющие условию. Экономит место и ускоряет обновления для редко запрашиваемых подмножеств. - TTL-индекс (
{ createdAt: 1 }, { expireAfterSeconds: 86400 }) — автоматически удаляет документы, возраст которых превышает указанное число секунд. Используется для логов, сессий, временных задач. - Текстовый индекс (
{ title: "text", body: "text" }) — поддерживает полнотекстовый поиск с ранжированием по релевантности ({ $meta: "textScore" }). - Геопространственные индексы (
2dsphere,2d) — для запросов вида «найти точки в радиусе», «ближайшие объекты».
Принципы эффективного индексирования
- Покрывающие индексы — когда все запрашиваемые поля присутствуют в индексе. MongoDB может выполнить запрос, не обращаясь к самим документам (FETCH stage отсутствует).
- Выборочность — индекс эффективен, если отсекает значительную часть данных. Индекс по полю
gender(2 значения) почти бесполезен при фильтреgender: "M", если половина коллекции — мужчины. - Накладные расходы — каждый индекс замедляет вставку, обновление и удаление (т.к. нужно обновлять и структуру индекса). Оптимальное число индексов — 3–7 на коллекцию в большинстве сценариев.
- Комбинирование фильтрации, сортировки и проекции — индекс может обслуживать все три этапа одновременно, если его структура соответствует порядку: равенство → сортировка → диапазон (правило ESR: Equality, Sort, Range).
Анализ эффективности индексов проводится через explain(), а также команды db.collection.totalIndexSize(), db.collection.stats().
Агрегация данных
Агрегация в MongoDB — это механизм преобразования, фильтрации, группировки и вычисления данных непосредственно на стороне сервера. В отличие от клиентской обработки результата find(), агрегация минимизирует объём передаваемых данных и использует оптимизированные внутренние алгоритмы, что критично для производительности при работе с миллионами записей.
Основная абстракция — конвейер (pipeline): последовательность этапов (stages), через которые проходят документы. Каждый этап принимает поток документов, применяет к ним преобразование и передаёт результат следующему этапу. Конвейер задаётся массивом BSON-объектов, где каждый объект соответствует одному этапу и имеет ровно одно поле с ключом, начинающимся с $.
Базовые этапы конвейера
-
$match— фильтрация документов по условию, аналогичному параметруfind().{ $match: { status: "active", createdAt: { $gte: ISODate("2025-01-01") } } }Применяется как можно раньше в конвейере: уменьшает объём данных на последующих этапах и позволяет использовать индексы.
-
$project— переопределение структуры документа: включение, исключение, вычисление новых полей.{ $project: {
name: 1,
emailDomain: { $split: ["$email", "@"] },
isAdult: { $gte: ["$age", 18] }
} }Поддерживает арифметические, строковые, логические, датовые и условные операторы (
$add,$substr,$cond,$dateToString,$size,$typeи др.). Можно использовать$projectдля «разворачивания» вложенных объектов ($mergeObjects) или упрощения структуры перед$group. -
$group— агрегация документов по указанному ключу (или_id: nullдля глобальной группировки) с применением аккумуляторных операторов.{ $group: {
_id: "$department",
avgSalary: { $avg: "$salary" },
totalEmployees: { $sum: 1 },
maxSalary: { $max: "$salary" },
employees: { $push: "$name" } // собирает все имена в массив
} }Поддерживаемые аккумуляторы:
$sum,$avg,$min,$max,$first,$last,$push,$addToSet,$stdDevPop,$stdDevSamp. -
$sort— упорядочивание документов. Работает идентично.sort()у курсора, но в рамках конвейера может потребоватьallowDiskUse: true, если объём данных превышает лимит памяти (100 МБ по умолчанию).
Часто используется перед$limitдля выборки топ-N. -
$skipи$limit— пагинация результатов. Как и при работе с курсорами,skipнеэффективен при больших смещениях; для глубокой пагинации предпочтителен паттерн «range-based pagination» с$matchпо последнему известному значению сортируемого поля.
Продвинутые этапы
-
$unwind— развёртывание массива: на каждый элемент массива создаётся отдельный документ. Удобен для анализа вложенных коллекций.{ $unwind: "$tags" } // { _id: 1, tags: ["A", "B"] } → два документа: { _id: 1, tags: "A" }, { _id: 1, tags: "B" }Опция
preserveNullAndEmptyArrays: trueсохраняет документы, где поле отсутствует,nullили пустой массив. -
$lookup— выполнение операции, аналогичной left outer join в SQL. Позволяет объединять данные из разных коллекций без денормализации.{ $lookup: {
from: "orders",
localField: "_id",
foreignField: "customerId",
as: "orders"
} }Используется с осторожностью: при отсутствии индекса по
foreignFieldможет привести к полному сканированию целевой коллекции на каждый документ. -
$facet— одновременный запуск нескольких независимых подконвейеров и объединение их результатов в один документ. Применяется для построения сложных отчётов: например, агрегация + распределение по интервалам + топ-10 — всё в одном запросе.{ $facet: {
summary: [ { $group: { _id: null, total: { $sum: 1 } } } ],
byStatus: [ { $group: { _id: "$status", count: { $sum: 1 } } } ],
topAuthors: [ { $sort: { views: -1 } }, { $limit: 5 } ]
} } -
$bucketи$bucketAuto— автоматическое распределение документов по диапазонам (bucketing). Полезно для построения гистограмм.{ $bucket: {
groupBy: "$price",
boundaries: [0, 100, 500, 1000, Infinity],
default: "other",
output: { count: { $sum: 1 } }
} } -
$setWindowFields(начиная с MongoDB 5.0) — оконные функции: вычисление скользящих средних, рангов, накопительных сумм без группировки.{ $setWindowFields: {
partitionBy: "$department",
sortBy: { salary: -1 },
output: {
rank: { $rank: {} },
runningTotal: { $sum: "$salary", window: { documents: ["unbounded", "current"] } }
}
} }
Конвейер оптимизируется автоматически: MongoDB переставляет этапы, чтобы максимально использовать индексы (например, sort + limit может быть заменён на «top-k» операцию с индексом), убирает избыточные project, объединяет последовательные $match.
Для отладки используется explain("executionStats") на агрегации — возвращает план выполнения, объём данных на каждом этапе, время обработки и использование индексов.
Транзакции
До версии 4.0 MongoDB поддерживала только одно-документные атомарные операции. С выходом 4.0 появилась поддержка много-документных транзакций с гарантиями ACID (Atomicity, Consistency, Isolation, Durability), сначала для реплика-сетов, а с 4.2 — и для шардированных кластеров.
Транзакции реализованы на уровне сессии — объекта, представляющего клиентский контекст. Сессия создаётся явно (client.startSession()) или неявно (при использовании withTransaction() в драйверах). Все операции в рамках транзакции выполняются в одной сессии.
Пример (синтаксис mongosh, псевдокод):
const session = db.getMongo().startSession();
session.startTransaction();
try {
db.accounts.updateOne({ _id: "A" }, { $inc: { balance: -100 } }, { session });
db.accounts.updateOne({ _id: "B" }, { $inc: { balance: +100 } }, { session });
session.commitTransaction();
} catch (error) {
session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Ключевые ограничения и особенности
- Время жизни: транзакция автоматически прерывается, если не завершена в течение 60 секунд (по умолчанию; настраивается через
transactionLifetimeLimitSeconds). Продление невозможно. - Размер лога (oplog): суммарный объём изменений в транзакции не должен превышать 16 МБ (размер одного документа в oplog). При превышении — ошибка.
- Совместимость с операциями: не поддерживаются операции, изменяющие схему (
createCollection,drop,createIndex), а такжеdistinct,mapReduce,geoNear,text searchвнутри транзакции. - Уровни изоляции: MongoDB использует snapshot isolation — транзакция работает с согласованным снимком данных на момент её начала. Читает не видят неподтверждённых записей других транзакций; писатели не блокируют читателей (MVCC на базе WiredTiger).
- Производительность: транзакции накладывают издержки — логирование в
transaction table, управление снапшотами. Их следует использовать только там, где требуется строгая согласованность (денежные переводы, инвентаризация), а не «на всякий случай».
Для большинства сценариев (например, обновление профиля пользователя) достаточно одно-документной атомарности (updateOne с $set, $inc, $push и т.д.), так как документ сам по себе — естественная граница согласованности.
Практическое задание
Цель: ознакомиться с графическим интерфейсом, визуально исследовать структуру данных, выполнить CRUD-операции и проанализировать производительность запросов.
Шаг 1. Установка
- Перейдите на официальную страницу загрузок: https://www.mongodb.com/try/download/compass
- Выберите версию, соответствующую вашей ОС (Windows, macOS, Linux).
- Запустите установщик и следуйте инструкциям (на Linux — распакуйте архив и запустите бинарный файл).
Примечание: Compass не требует предварительной установки MongoDB Server, но для работы ему нужен запущенный
mongod(локальный или удалённый, например, Atlas).
Шаг 2. Подключение
- Откройте Compass.
- В поле Connection String введите:
- Для локального сервера по умолчанию:
mongodb://localhost:27017 - Для MongoDB Atlas: строка подключения из панели Atlas (вида
mongodb+srv://<user>:<password>@cluster0.xxx.mongodb.net/).
- Для локального сервера по умолчанию:
- Нажмите Connect.
Шаг 3. Исследование базы данных
- После подключения вы увидите список баз данных слева.
- Выберите базу
sample_mflix(если используете Atlas) или создайте свою (CREATE DATABASE). - Перейдите в коллекцию
movies. - Вкладка Documents показывает первые 20 документов. Используйте фильтр вверху (
Filter) для поиска:{ "year": { "$gte": 2000 }, "imdb.rating": { "$gte": 8.5 } } - Нажмите Explain Plan, чтобы увидеть, какой индекс использован, сколько документов просканировано.
Шаг 4. Создание и обновление данных
- Нажмите Insert Document → Insert Document.
- Вставьте JSON:
{
"title": "Практическое задание",
"year": 2025,
"genres": ["учеба", "IT"],
"plot": "Первая запись через Compass"
} - Нажмите Insert.
- Найдите этот документ по
title, откройте его и нажмите Edit Document. Добавьте полеcompleted: true. Сохраните.
Шаг 5. Агрегация через Compass
- Перейдите во вкладку Aggregations.
- Нажмите Add Stage → выберите
$match:{ "year": { "$gte": 2000 } } - Добавьте
$group:{
"_id": "$genres",
"count": { "$sum": 1 }
} - Нажмите Run, чтобы увидеть распределение фильмов по жанрам.
- Добавьте
$sort:{ "count": -1 }— результат отсортируется по убыванию.
Шаг 6. Анализ и индексы
- Перейдите во вкладку Indexes.
- Нажмите Create Index.
- Укажите поле
year: 1, поставьте галочку Unique (снимите, если значения не уникальны), нажмите Create Index. - Вернитесь в Documents, выполните прежний фильтр — в Explain Plan теперь должен отображаться
IXSCAN, а неCOLLSCAN.
Это задание даёт практическое понимание связи между структурой данных, запросами, индексами и интерфейсом.
Сравнение с реляционными СУБД
MongoDB и классические реляционные СУБД (PostgreSQL, MySQL, SQL Server) решают схожие задачи — хранение, обработка и обеспечение целостности данных, — но делают это с разными акцентами. Отсутствие JOIN’ов, отсутствие строгой схемы и отсутствие табличных нормальных форм в MongoDB — осознанные архитектурные решения, оптимизированные под определённые рабочие нагрузки.
В реляционных системах данные нормализуются для устранения дублирования и обеспечения целостности. Это требует множественных JOIN’ов при чтении, но упрощает обновление: изменение в одном месте распространяется автоматически. В MongoDB предпочтение отдаётся денормализации и вложению — ради уменьшения числа чтений и повышения скорости ответа. Это увеличивает объём хранимых данных и усложняет обновление, но критически важно для высоконагруженных веб- и мобильных приложений, где время отклика — ключевой метрик.
Выбор между моделями следует делать по следующим критериям:
- Изменчивость схемы: если структура данных постоянно эволюционирует в процессе разработки или эксплуатации — документная модель снижает издержки на миграции.
- Сложность связей: если домен характеризуется глубокой иерархией (например, заказ → позиции → детали → свойства), вложенные документы отражают её естественно. Если связи плоские и равноправные (например, «пациент — врач — приём — диагноз»), реляционная модель может быть проще.
- Требования к согласованности: если бизнес-логика требует строгих транзакций между множеством сущностей (банковские переводы, бухгалтерия), реляционные СУБД исторически сильнее. Однако с поддержкой много-документных транзакций MongoDB закрыла этот разрыв для большинства прикладных сценариев.
- Масштабируемость «по горизонтали»: MongoDB изначально проектировалась для шардирования. Добавление новых узлов и перераспределение данных — штатная операция. В реляционных системах горизонтальное масштабирование требует значительных усилий (Citus, Vitess, шардирование на уровне приложения).
На практике многие современные системы используют гибридный подход: основной поток событий и операционные данные — в MongoDB, а аналитика, отчётность и регуляторные отчёты — в реляционных или OLAP-хранилищах (через CDC, Change Streams, ETL).
Типовые сценарии применения MongoDB
Социальные сети и контент-платформы
Посты, комментарии, лайки, профили пользователей — естественно моделируются как вложенные или связанные документы. Например, пост может содержать массив комментариев, каждый из которых — объект с автором, временем, текстом и, возможно, вложенными ответами. Обновление счётчиков (число лайков) выполняется атомарно через $inc. Для поиска по тегам или тексту используется текстовый индекс или интеграция с Atlas Search.
Каталоги товаров и электронная коммерция
Продукты часто имеют неоднородные атрибуты: у ноутбука — процессор, ОЗУ, видеокарта; у книги — автор, ISBN, жанр. В MongoDB каждый товар может храниться как единый документ с полем specifications, содержащим произвольный набор пар «ключ-значение». Фильтрация по цене, рейтингу, наличию — через индексы. Рекомендации строятся на основе агрегации истории просмотров и покупок.
IoT и временные ряды
Сенсоры генерируют потоки данных с отметкой времени. MongoDB поддерживает коллекции временных рядов (начиная с 5.0) — оптимизированный тип коллекции, где документы группируются по meta (идентификатор устройства) и time, обеспечивая эффективное хранение и запросы по диапазонам времени. Агрегация позволяет строить скользящие средние, детектировать аномалии ($setWindowFields, $stdDevSamp).
Логирование и аналитика событий
Каждое событие (запрос API, клик, ошибка) записывается как документ. Благодаря гибкости схемы, события разных типов могут храниться в одной коллекции с разным набором полей. TTL-индексы автоматически удаляют старые записи. Агрегация позволяет строить dashboards в реальном времени: DAU/MAU, funnel-анализ, топ-10 медленных запросов.
Генеративный ИИ и векторный поиск
MongoDB Atlas предоставляет векторные индексы (на основе HNSW), позволяя хранить и искать эмбеддинги (векторные представления текста, изображений) в той же коллекции, что и операционные данные. Это устраняет необходимость синхронизации между operational и vector-базами. Семантический поиск, рекомендации, RAG (Retrieval-Augmented Generation) реализуются через этап $vectorSearch в агрегационном конвейере — без выгрузки данных во внешние сервисы.
Проектирование схемы
MongoDB не навязывает единственный способ моделирования связей. Разработчик выбирает между:
-
Вложением (embedding) — дочерние документы хранятся внутри родительского.
Преимущества: атомарная запись, одиночное чтение, естественное отражение иерархии.
Ограничения: размер документа ≤ 16 МБ; обновление вложенных элементов требует$или$[]; невозможно индексировать отдельные элементы массива без$**. -
Ссылками (referencing) — хранение
_idсвязанного документа как поля.
Преимущества: отсутствие ограничений по размеру; независимое управление жизненным циклом; поддержка циклических связей.
Недостатки: требуется несколько запросов или$lookupдля извлечения связанных данных; отсутствие атомарности между коллекциями без транзакции. -
Гибридным подходом — частичное вложение «горячих» данных (например, имя и аватар автора поста), а остальное — по ссылке. Это баланс между производительностью и гибкостью.
Рекомендации по выбору:
- Вкладывайте, если связь «один-ко-многим» и дочерние объекты всегда запрашиваются вместе с родителем (комментарии под постом, пункты заказа).
- Ссылайтесь, если связь «многие-ко-многим» или дочерние объекты используются автономно (пользователи и роли, товары и категории).
- Используйте частичное вложение для denormalization часто читаемых, редко изменяемых атрибутов (например,
authorNameв посте, даже если полный профиль — отдельный документ).
MongoDB также поддерживает валидацию схемы на уровне коллекции через JSON Schema. Это позволяет внедрить частичную строгость: например, требовать наличие поля email, проверять его формат регулярным выражением, задавать enum для status. Валидация применяется при вставке и обновлении, но не мешает добавлять опциональные расширения — гибкость сохраняется.
Антипаттерны и типичные ошибки
-
Хранение больших бинарных объектов в
BinData— файлы (изображения, видео) размером более нескольких мегабайт следует хранить в объектных хранилищах (S3, MinIO), а в MongoDB оставлять только метаданные и URL. Для файлов до 16 МБ можно использовать GridFS — специализированный механизм, разбивающий файл на чанки. -
Чрезмерная вложенность — вложенные объекты глубже 3–4 уровней усложняют запросы (
field.subfield.subsubfield.value), затрудняют индексацию и увеличивают риск превышения лимита в 16 МБ. При необходимости — плоская структура с составными ключами. -
Отсутствие TTL для временных данных — логи, сессии, кэши должны автоматически удаляться. Без TTL-индекса накопление данных приведёт к росту дискового пространства и замедлению операций.
-
Использование
skipдля глубокой пагинации —skip(100000)требует сканирования 100 000 документов. Альтернатива: курсорная пагинация по индексируемому полю (например,createdAt), где клиент передаёт последнее значение как маркер. -
Создание индексов «на всякий случай» — каждый индекс замедляет запись. Регулярно анализируйте использование индексов через
indexStatsи удаляйте неиспользуемые. -
Попытка имитировать JOIN’ы через вложенные
$lookupв каждом запросе — если связанные данные нужны постоянно, лучше денормализовать.$lookup— для редких отчётов, а не для hot-path.